/**
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { newError, ProtocolVersion } from '../../core/index.ts'
import BoltProtocolV1 from './bolt-protocol-v1.js'
import BoltProtocolV2 from './bolt-protocol-v2.js'
import BoltProtocolV3 from './bolt-protocol-v3.js'
import BoltProtocolV4x0 from './bolt-protocol-v4x0.js'
import BoltProtocolV4x1 from './bolt-protocol-v4x1.js'
import BoltProtocolV4x2 from './bolt-protocol-v4x2.js'
import BoltProtocolV4x3 from './bolt-protocol-v4x3.js'
import BoltProtocolV4x4 from './bolt-protocol-v4x4.js'
import BoltProtocolV5x0 from './bolt-protocol-v5x0.js'
import BoltProtocolV5x1 from './bolt-protocol-v5x1.js'
import BoltProtocolV5x2 from './bolt-protocol-v5x2.js'
import BoltProtocolV5x3 from './bolt-protocol-v5x3.js'
import BoltProtocolV5x4 from './bolt-protocol-v5x4.js'
import BoltProtocolV5x5 from './bolt-protocol-v5x5.js'
import BoltProtocolV5x6 from './bolt-protocol-v5x6.js'
import BoltProtocolV5x7 from './bolt-protocol-v5x7.js'
import BoltProtocolV5x8 from './bolt-protocol-v5x8.js'
import BoltProtocolV6x0 from './bolt-protocol-v6x0.js'
// eslint-disable-next-line no-unused-vars
import { Chunker, Dechunker } from '../channel/index.js'
import ResponseHandler from './response-handler.js'

/**
 * Creates a protocol with a given version
 *
 * @param {object} config
 * @param {number} config.version The version of the protocol
 * @param {channel} config.channel The channel
 * @param {Chunker} config.chunker The chunker
 * @param {Dechunker} config.dechunker The dechunker
 * @param {Logger} config.log The logger
 * @param {ResponseHandler~Observer} config.observer Observer
 * @param {boolean} config.disableLosslessIntegers Disable the lossless integers
 * @param {boolean} packstreamConfig.useBigInt if this connection should convert all received integers to native BigInt numbers.
 * @param {boolean} config.serversideRouting It's using server side routing
 */
export default function create ({
  version,
  chunker,
  dechunker,
  channel,
  disableLosslessIntegers,
  useBigInt,
  serversideRouting,
  server, // server info
  log,
  observer
} = {}) {
  const createResponseHandler = protocol => {
    const responseHandler = new ResponseHandler({
      transformMetadata: protocol.transformMetadata.bind(protocol),
      enrichErrorMetadata: protocol.enrichErrorMetadata.bind(protocol),
      log,
      observer
    })

    // reset the error handler to just handle errors and forget about the handshake promise
    channel.onerror = observer.onError.bind(observer)

    // Ok, protocol running. Simply forward all messages to the dechunker
    channel.onmessage = buf => dechunker.write(buf)

    // setup dechunker to dechunk messages and forward them to the message handler
    dechunker.onmessage = buf => {
      try {
        responseHandler.handleResponse(protocol.unpack(buf))
      } catch (e) {
        return observer.onError(e)
      }
    }

    return responseHandler
  }

  return createProtocol(
    version,
    server,
    chunker,
    { disableLosslessIntegers, useBigInt },
    serversideRouting,
    createResponseHandler,
    observer.onProtocolError.bind(observer),
    log
  )
}

function createProtocol (
  version,
  server,
  chunker,
  packingConfig,
  serversideRouting,
  createResponseHandler,
  onProtocolError,
  log
) {
  if (!(version instanceof ProtocolVersion) || version === undefined || version === null) {
    throw newError('Unknown Bolt protocol version: ' + version)
  }
  switch (true) {
    case version.isEqualTo({ major: 1, minor: 0 }):
      return new BoltProtocolV1(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError
      )
    case version.isEqualTo({ major: 2, minor: 0 }):
      return new BoltProtocolV2(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError
      )
    case version.isEqualTo({ major: 3, minor: 0 }):
      return new BoltProtocolV3(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError
      )
    case version.isEqualTo({ major: 4, minor: 0 }):
      return new BoltProtocolV4x0(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError
      )
    case version.isEqualTo({ major: 4, minor: 1 }):
      return new BoltProtocolV4x1(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting
      )
    case version.isEqualTo({ major: 4, minor: 2 }):
      return new BoltProtocolV4x2(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting
      )
    case version.isEqualTo({ major: 4, minor: 3 }):
      return new BoltProtocolV4x3(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting
      )
    case version.isEqualTo({ major: 4, minor: 4 }):
      return new BoltProtocolV4x4(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting
      )
    case version.isEqualTo({ major: 5, minor: 0 }):
      return new BoltProtocolV5x0(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting
      )
    case version.isEqualTo({ major: 5, minor: 1 }):
      return new BoltProtocolV5x1(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting
      )
    case version.isEqualTo({ major: 5, minor: 2 }):
      return new BoltProtocolV5x2(
        server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting
      )
    case version.isEqualTo({ major: 5, minor: 3 }):
      return new BoltProtocolV5x3(server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting)
    case version.isEqualTo({ major: 5, minor: 4 }):
      return new BoltProtocolV5x4(server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting)
    case version.isEqualTo({ major: 5, minor: 5 }):
      return new BoltProtocolV5x5(server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting)
    case version.isEqualTo({ major: 5, minor: 6 }):
      return new BoltProtocolV5x6(server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting)
    case version.isEqualTo({ major: 5, minor: 7 }):
      return new BoltProtocolV5x7(server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting)
    case version.isEqualTo({ major: 5, minor: 8 }):
      return new BoltProtocolV5x8(server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting)
    case version.isEqualTo({ major: 6, minor: 0 }):
      return new BoltProtocolV6x0(server,
        chunker,
        packingConfig,
        createResponseHandler,
        log,
        onProtocolError,
        serversideRouting)
    default:
      throw newError('Unknown Bolt protocol version: ' + version)
  }
}
